"""BLE GATT Server implementation."""

import logging
import dbus
import dbus.service
import dbus.mainloop.glib
from gi.repository import GLib
from typing import Optional, Dict, Any, List
import array
import threading
import time

from src.ble.constants import (
    SERVICE_UUID,
    CHAR_COMMAND_UUID,
    CHAR_RESPONSE_UUID,
    CHAR_STATUS_UUID,
    CHAR_NOTIFICATION_UUID,
    CHAR_FLAGS_WRITE,
    CHAR_FLAGS_READ_NOTIFY,
    CHAR_FLAGS_NOTIFY,
    DBUS_APPLICATION_PATH,
    DBUS_SERVICE_NAME,
    DBUS_GATT_SERVICE_INTERFACE,
    DBUS_GATT_CHARACTERISTIC_INTERFACE,
    DBUS_LE_ADVERTISEMENT_INTERFACE,
    ADV_NAME_PREFIX,
    ADV_INTERVAL_DEFAULT,
    STARTUP_TIMEOUT
)
from src.ble.connection import ConnectionManager, ConnectionState
from src.ble.protocol import ProtocolHandler
from src.ble.notification import StatusNotificationManager, StatusCode
from src.utils.dbus_helper import DBusHelper, get_device_mac

logger = logging.getLogger(__name__)


class Advertisement(dbus.service.Object):
    """BLE Advertisement."""
    
    PATH_BASE = f"{DBUS_APPLICATION_PATH}/advertisement"
    
    def __init__(self, bus, index, advertising_type="peripheral"):
        """
        Initialize advertisement.
        
        Args:
            bus: DBus connection
            index: Advertisement index
            advertising_type: Type of advertisement
        """
        self.path = f"{self.PATH_BASE}{index}"
        self.bus = bus
        self.ad_type = advertising_type
        self.service_uuids = [SERVICE_UUID]
        self.local_name = self._generate_device_name()
        self.include_tx_power = True
        self.data = {}
        
        dbus.service.Object.__init__(self, bus, self.path)
        
    def _generate_device_name(self) -> str:
        """Generate device name with MAC suffix."""
        mac = get_device_mac()
        if mac:
            suffix = mac.replace(":", "")[-4:].upper()
            return f"{ADV_NAME_PREFIX}_{suffix}"
        return ADV_NAME_PREFIX
        
    def get_properties(self):
        """Get advertisement properties."""
        properties = {
            "Type": self.ad_type,
            "ServiceUUIDs": dbus.Array(self.service_uuids, signature='s'),
            "LocalName": dbus.String(self.local_name),
            "IncludeTxPower": dbus.Boolean(self.include_tx_power)
        }
        
        if self.data:
            properties["ServiceData"] = dbus.Dictionary(self.data, signature='sv')
            
        return properties
        
    @dbus.service.method(dbus.PROPERTIES_IFACE,
                         in_signature='ss',
                         out_signature='v')
    def Get(self, interface, prop):
        """Get property."""
        if interface != DBUS_LE_ADVERTISEMENT_INTERFACE:
            raise dbus.exceptions.DBusException(
                f"org.bluez.Error.InvalidArguments: Invalid interface {interface}"
            )
            
        properties = self.get_properties()
        if prop in properties:
            return properties[prop]
            
        raise dbus.exceptions.DBusException(
            f"org.bluez.Error.InvalidArguments: Invalid property {prop}"
        )
        
    @dbus.service.method(dbus.PROPERTIES_IFACE,
                         in_signature='s',
                         out_signature='a{sv}')
    def GetAll(self, interface):
        """Get all properties."""
        if interface != DBUS_LE_ADVERTISEMENT_INTERFACE:
            raise dbus.exceptions.DBusException(
                f"org.bluez.Error.InvalidArguments: Invalid interface {interface}"
            )
            
        return self.get_properties()
        
    @dbus.service.method(DBUS_LE_ADVERTISEMENT_INTERFACE,
                         in_signature='',
                         out_signature='')
    def Release(self):
        """Release advertisement."""
        logger.info(f"Advertisement released: {self.path}")


class Characteristic(dbus.service.Object):
    """BLE GATT Characteristic."""
    
    def __init__(self, bus, index, uuid, flags, service, 
                 on_read=None, on_write=None,
                 on_notify_start=None, on_notify_stop=None):
        """
        Initialize characteristic.
        
        Args:
            bus: DBus connection
            index: Characteristic index
            uuid: Characteristic UUID
            flags: Characteristic flags
            service: Parent service
            on_read: Read callback
            on_write: Write callback
            on_notify_start: Notification start callback
            on_notify_stop: Notification stop callback
        """
        self.path = f"{service.path}/char{index}"
        self.bus = bus
        self.uuid = uuid
        self.flags = flags
        self.service = service
        self.value = []
        self.notifying = False
        self.on_read = on_read
        self.on_write = on_write
        self.on_notify_start = on_notify_start
        self.on_notify_stop = on_notify_stop
        
        dbus.service.Object.__init__(self, bus, self.path)
        
    def get_properties(self):
        """Get characteristic properties."""
        return {
            "Service": self.service.path,
            "UUID": self.uuid,
            "Flags": self.flags,
            "Value": dbus.Array(self.value, signature='y')
        }
        
    @dbus.service.method(dbus.PROPERTIES_IFACE,
                         in_signature='ss',
                         out_signature='v')
    def Get(self, interface, prop):
        """Get property."""
        if interface != DBUS_GATT_CHARACTERISTIC_INTERFACE:
            raise dbus.exceptions.DBusException(
                f"org.bluez.Error.InvalidArguments: Invalid interface {interface}"
            )
            
        properties = self.get_properties()
        if prop in properties:
            return properties[prop]
            
        raise dbus.exceptions.DBusException(
            f"org.bluez.Error.InvalidArguments: Invalid property {prop}"
        )
        
    @dbus.service.method(dbus.PROPERTIES_IFACE,
                         in_signature='s',
                         out_signature='a{sv}')
    def GetAll(self, interface):
        """Get all properties."""
        if interface != DBUS_GATT_CHARACTERISTIC_INTERFACE:
            raise dbus.exceptions.DBusException(
                f"org.bluez.Error.InvalidArguments: Invalid interface {interface}"
            )
            
        return self.get_properties()
        
    @dbus.service.method(DBUS_GATT_CHARACTERISTIC_INTERFACE,
                         in_signature='a{sv}',
                         out_signature='ay')
    def ReadValue(self, options):
        """Read characteristic value."""
        logger.debug(f"ReadValue called on {self.uuid}")
        
        if self.on_read:
            try:
                data = self.on_read()
                if isinstance(data, bytes):
                    self.value = [dbus.Byte(b) for b in data]
                elif isinstance(data, str):
                    self.value = [dbus.Byte(b) for b in data.encode('utf-8')]
            except Exception as e:
                logger.error(f"Read callback error: {e}")
                
        return self.value
        
    @dbus.service.method(DBUS_GATT_CHARACTERISTIC_INTERFACE,
                         in_signature='aya{sv}',
                         out_signature='')
    def WriteValue(self, value, options):
        """Write characteristic value."""
        logger.debug(f"WriteValue called on {self.uuid}: {len(value)} bytes")
        
        self.value = value
        
        if self.on_write:
            try:
                data = bytes(value)
                self.on_write(data)
            except Exception as e:
                logger.error(f"Write callback error: {e}")
                raise dbus.exceptions.DBusException(
                    f"org.bluez.Error.Failed: Write failed: {e}"
                )
                
    @dbus.service.method(DBUS_GATT_CHARACTERISTIC_INTERFACE,
                         in_signature='',
                         out_signature='')
    def StartNotify(self):
        """Start notifications."""
        if self.notifying:
            logger.debug("Already notifying")
            return
            
        self.notifying = True
        logger.info(f"Started notifying on {self.uuid}")
        
        # Call notification start callback if provided
        if hasattr(self, 'on_notify_start') and self.on_notify_start:
            try:
                # Get device path from DBus context if available
                device_path = self._get_device_path()
                self.on_notify_start(device_path)
            except Exception as e:
                logger.error(f"Notify start callback error: {e}")
        
    @dbus.service.method(DBUS_GATT_CHARACTERISTIC_INTERFACE,
                         in_signature='',
                         out_signature='')
    def StopNotify(self):
        """Stop notifications."""
        if not self.notifying:
            logger.debug("Not notifying")
            return
            
        self.notifying = False
        logger.info(f"Stopped notifying on {self.uuid}")
        
        # Call notification stop callback if provided
        if hasattr(self, 'on_notify_stop') and self.on_notify_stop:
            try:
                device_path = self._get_device_path()
                self.on_notify_stop(device_path)
            except Exception as e:
                logger.error(f"Notify stop callback error: {e}")
                
    def _get_device_path(self) -> str:
        """Get the device path from DBus context."""
        # In a real implementation, this would extract the device path
        # from the DBus method context. For now, return a placeholder.
        return "default"
        
    def update_value(self, value):
        """Update characteristic value and send notification if enabled."""
        if isinstance(value, bytes):
            self.value = [dbus.Byte(b) for b in value]
        elif isinstance(value, str):
            self.value = [dbus.Byte(b) for b in value.encode('utf-8')]
        else:
            self.value = value
            
        if self.notifying:
            self.PropertiesChanged(
                DBUS_GATT_CHARACTERISTIC_INTERFACE,
                {"Value": self.value},
                []
            )
            
    @dbus.service.signal(dbus.PROPERTIES_IFACE, signature='sa{sv}as')
    def PropertiesChanged(self, interface, changed, invalidated):
        """Properties changed signal."""
        pass


class Service(dbus.service.Object):
    """BLE GATT Service."""
    
    PATH_BASE = f"{DBUS_APPLICATION_PATH}/service"
    
    def __init__(self, bus, index, uuid, primary=True):
        """
        Initialize service.
        
        Args:
            bus: DBus connection
            index: Service index
            uuid: Service UUID
            primary: Whether this is a primary service
        """
        self.path = f"{self.PATH_BASE}{index}"
        self.bus = bus
        self.uuid = uuid
        self.primary = primary
        self.characteristics = []
        
        dbus.service.Object.__init__(self, bus, self.path)
        
    def get_properties(self):
        """Get service properties."""
        return {
            "UUID": self.uuid,
            "Primary": self.primary,
            "Characteristics": dbus.Array([c.path for c in self.characteristics], signature='o')
        }
        
    @dbus.service.method(dbus.PROPERTIES_IFACE,
                         in_signature='ss',
                         out_signature='v')
    def Get(self, interface, prop):
        """Get property."""
        if interface != DBUS_GATT_SERVICE_INTERFACE:
            raise dbus.exceptions.DBusException(
                f"org.bluez.Error.InvalidArguments: Invalid interface {interface}"
            )
            
        properties = self.get_properties()
        if prop in properties:
            return properties[prop]
            
        raise dbus.exceptions.DBusException(
            f"org.bluez.Error.InvalidArguments: Invalid property {prop}"
        )
        
    @dbus.service.method(dbus.PROPERTIES_IFACE,
                         in_signature='s',
                         out_signature='a{sv}')
    def GetAll(self, interface):
        """Get all properties."""
        if interface != DBUS_GATT_SERVICE_INTERFACE:
            raise dbus.exceptions.DBusException(
                f"org.bluez.Error.InvalidArguments: Invalid interface {interface}"
            )
            
        return self.get_properties()
        
    def add_characteristic(self, uuid, flags, on_read=None, on_write=None,
                          on_notify_start=None, on_notify_stop=None):
        """Add a characteristic to the service."""
        char = Characteristic(
            self.bus,
            len(self.characteristics),
            uuid,
            flags,
            self,
            on_read,
            on_write,
            on_notify_start,
            on_notify_stop
        )
        self.characteristics.append(char)
        return char


class Application(dbus.service.Object):
    """BLE GATT Application."""
    
    def __init__(self, bus):
        """Initialize application."""
        self.path = DBUS_APPLICATION_PATH
        self.services = []
        dbus.service.Object.__init__(self, bus, self.path)
        
    def add_service(self, service):
        """Add a service to the application."""
        self.services.append(service)
        
    @dbus.service.method("org.freedesktop.DBus.ObjectManager",
                         in_signature='',
                         out_signature='a{oa{sa{sv}}}')
    def GetManagedObjects(self):
        """Get managed objects."""
        response = {}
        
        for service in self.services:
            response[service.path] = {
                DBUS_GATT_SERVICE_INTERFACE: service.get_properties()
            }
            
            for char in service.characteristics:
                response[char.path] = {
                    DBUS_GATT_CHARACTERISTIC_INTERFACE: char.get_properties()
                }
                
        return response


class BLEGATTServer:
    """Main BLE GATT Server."""
    
    def __init__(self, config: Optional[Dict[str, Any]] = None):
        """
        Initialize BLE GATT Server.
        
        Args:
            config: Configuration dictionary
        """
        self.config = config or {}
        self.running = False
        self.mainloop = None
        self.dbus_helper = DBusHelper()
        self.connection_manager = ConnectionManager()
        self.protocol_handler = ProtocolHandler(self._get_device_info)
        
        # Initialize notification manager
        language = self.config.get('language', 'zh_CN')
        self.notification_manager = StatusNotificationManager(language)
        
        self.application = None
        self.advertisement = None
        self.service = None
        self.char_command = None
        self.char_response = None
        self.char_status = None
        self.char_notification = None
        self._start_time = None
        
    def start(self):
        """Start the BLE GATT server."""
        if self.running:
            logger.warning("Server already running")
            return
            
        try:
            self._start_time = time.time()
            
            # Initialize DBus
            dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
            
            # Get adapter
            adapter = self.dbus_helper.get_adapter()
            if not adapter:
                raise RuntimeError("No Bluetooth adapter found")
                
            # Set device name
            device_name = self._generate_device_name()
            self.dbus_helper.set_alias(device_name)
            
            # Start connection manager
            self.connection_manager.start()
            
            # Setup GATT application
            self._setup_gatt_application()
            
            # Register application
            self._register_application()
            
            # Setup advertisement
            self._setup_advertisement()
            
            # Start advertising
            self._start_advertising()
            
            # Check startup time
            startup_time = time.time() - self._start_time
            if startup_time > STARTUP_TIMEOUT:
                logger.warning(f"Startup took {startup_time:.2f}s (limit: {STARTUP_TIMEOUT}s)")
            else:
                logger.info(f"Server started in {startup_time:.2f}s")
                
            self.running = True
            
            # Update connection state
            self.connection_manager.set_state(ConnectionState.ADVERTISING)
            
            # Start main loop
            self.mainloop = GLib.MainLoop()
            logger.info("BLE GATT Server running")
            self.mainloop.run()
            
        except Exception as e:
            logger.error(f"Failed to start server: {e}")
            self.stop()
            raise
            
    def stop(self):
        """Stop the BLE GATT server."""
        logger.info("Stopping BLE GATT Server")
        
        try:
            # Stop advertising
            self._stop_advertising()
            
            # Unregister application
            self._unregister_application()
            
            # Stop connection manager
            self.connection_manager.stop()
            
            # Stop protocol handler
            self.protocol_handler.stop()
            
            # Stop notification manager
            self.notification_manager.stop()
            
            # Quit main loop
            if self.mainloop and self.mainloop.is_running():
                self.mainloop.quit()
                
            self.running = False
            logger.info("BLE GATT Server stopped")
            
        except Exception as e:
            logger.error(f"Error stopping server: {e}")
            
    def _generate_device_name(self) -> str:
        """Generate device name with MAC suffix."""
        mac = get_device_mac()
        if mac:
            suffix = mac.replace(":", "")[-4:].upper()
            return f"{ADV_NAME_PREFIX}_{suffix}"
        return ADV_NAME_PREFIX
        
    def _get_device_info(self) -> Dict[str, Any]:
        """Get device information for protocol handler."""
        return {
            "name": self._generate_device_name(),
            "uptime": int(time.time() - self._start_time) if self._start_time else 0,
            "connections": self.connection_manager.get_connection_count(),
            "state": self.connection_manager.get_state().value
        }
        
    def _setup_gatt_application(self):
        """Setup GATT application with service and characteristics."""
        bus = self.dbus_helper.get_bus()
        
        # Create application
        self.application = Application(bus)
        
        # Create service
        self.service = Service(bus, 0, SERVICE_UUID)
        
        # Add characteristics
        self.char_command = self.service.add_characteristic(
            CHAR_COMMAND_UUID,
            CHAR_FLAGS_WRITE,
            on_write=self._on_command_write
        )
        
        self.char_response = self.service.add_characteristic(
            CHAR_RESPONSE_UUID,
            CHAR_FLAGS_READ_NOTIFY,
            on_read=self._on_response_read
        )
        
        self.char_status = self.service.add_characteristic(
            CHAR_STATUS_UUID,
            CHAR_FLAGS_NOTIFY
        )
        
        # Add notification characteristic for real-time status updates
        self.char_notification = self.service.add_characteristic(
            CHAR_NOTIFICATION_UUID,
            CHAR_FLAGS_NOTIFY,
            on_notify_start=self._on_notification_subscribe,
            on_notify_stop=self._on_notification_unsubscribe
        )
        
        # Set notification callback for the manager
        self.notification_manager.set_notification_callback(
            self._send_notification_to_device
        )
        
        # Add service to application
        self.application.add_service(self.service)
        
        logger.info("GATT application setup complete")
        
    def _setup_advertisement(self):
        """Setup BLE advertisement."""
        bus = self.dbus_helper.get_bus()
        self.advertisement = Advertisement(bus, 0)
        logger.info(f"Advertisement setup: {self.advertisement.local_name}")
        
    def _register_application(self):
        """Register GATT application with BlueZ."""
        manager = self.dbus_helper.get_gatt_manager()
        if not manager:
            raise RuntimeError("Failed to get GATT Manager")
            
        try:
            manager.RegisterApplication(
                self.application.path,
                {},
                reply_handler=self._register_app_success,
                error_handler=self._register_app_error
            )
        except Exception as e:
            logger.error(f"Failed to register application: {e}")
            raise
            
    def _unregister_application(self):
        """Unregister GATT application."""
        manager = self.dbus_helper.get_gatt_manager()
        if manager and self.application:
            try:
                manager.UnregisterApplication(self.application.path)
                logger.info("Application unregistered")
            except Exception as e:
                logger.error(f"Failed to unregister application: {e}")
                
    def _start_advertising(self):
        """Start BLE advertising."""
        manager = self.dbus_helper.get_advertising_manager()
        if not manager:
            raise RuntimeError("Failed to get Advertising Manager")
            
        try:
            manager.RegisterAdvertisement(
                self.advertisement.path,
                {},
                reply_handler=self._register_ad_success,
                error_handler=self._register_ad_error
            )
        except Exception as e:
            logger.error(f"Failed to start advertising: {e}")
            raise
            
    def _stop_advertising(self):
        """Stop BLE advertising."""
        manager = self.dbus_helper.get_advertising_manager()
        if manager and self.advertisement:
            try:
                manager.UnregisterAdvertisement(self.advertisement.path)
                logger.info("Advertising stopped")
            except Exception as e:
                logger.error(f"Failed to stop advertising: {e}")
                
    def _register_app_success(self):
        """Application registration success callback."""
        logger.info("GATT application registered successfully")
        
    def _register_app_error(self, error):
        """Application registration error callback."""
        logger.error(f"Failed to register application: {error}")
        
    def _register_ad_success(self):
        """Advertisement registration success callback."""
        logger.info("Advertisement registered successfully")
        
    def _register_ad_error(self, error):
        """Advertisement registration error callback."""
        logger.error(f"Failed to register advertisement: {error}")
        
    def _on_command_write(self, data: bytes):
        """Handle write to command characteristic."""
        logger.debug(f"Command received: {len(data)} bytes")
        
        # Process command
        response = self.protocol_handler.handle_command(data)
        
        # Update response characteristic
        if self.char_response:
            self.char_response.update_value(response)
            
        # Update connection activity
        # Note: In real implementation, we'd get device path from DBus context
        self.connection_manager.update_activity("default")
        
    def _on_response_read(self) -> bytes:
        """Handle read from response characteristic."""
        logger.debug("Response read requested")
        # Return last response or default
        return b'{"status":"ready"}'
        
    def _update_status(self, status: str):
        """Update status characteristic."""
        if self.char_status:
            status_json = f'{{"status":"{status}","timestamp":"{time.time()}"}}'
            self.char_status.update_value(status_json.encode('utf-8'))
            
    def _on_notification_subscribe(self, device_path: str):
        """Handle notification subscription."""
        logger.info(f"Device subscribing to notifications: {device_path}")
        success = self.notification_manager.register_subscriber(device_path)
        
        if success:
            # Send initial status notification
            self.send_status_notification(StatusCode.IDLE)
        else:
            logger.warning(f"Failed to subscribe device: {device_path}")
            
    def _on_notification_unsubscribe(self, device_path: str):
        """Handle notification unsubscription."""
        logger.info(f"Device unsubscribing from notifications: {device_path}")
        self.notification_manager.unregister_subscriber(device_path)
        
    def _send_notification_to_device(self, data: bytes, device_path: str):
        """Send notification data to a specific device."""
        if self.char_notification and self.char_notification.notifying:
            self.char_notification.update_value(data)
            
    def send_status_notification(self, status_code: StatusCode, 
                                details: Optional[Dict[str, Any]] = None):
        """
        Send a status notification to all subscribed devices.
        
        Args:
            status_code: Status code to send
            details: Additional details to include
        """
        self.notification_manager.send_notification(status_code, details)


def main():
    """Main entry point."""
    import signal
    import sys
    
    # Setup logging
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )
    
    # Create server
    server = BLEGATTServer()
    
    # Handle signals
    def signal_handler(sig, frame):
        logger.info("Received signal, shutting down...")
        server.stop()
        sys.exit(0)
        
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    
    # Start server
    try:
        server.start()
    except Exception as e:
        logger.error(f"Server failed: {e}")
        sys.exit(1)


if __name__ == "__main__":
    main()